
module DrunkenMaster::Util
  class CodeStats

    def initialize options
      @root = options[:root]
      @pattern = options[:pattern] || /.*\.rb$/
      @test_pattern = options[:test_pattern] || /.*test\.rb$/
      @stats = []
    end

    def include_dir(label, rel_dir)
      @stats << stats_for_dir(@root + '/' + rel_dir, {
        :code => empty_stats, 
        :test => empty_stats}).merge(
      {
        :label => label
      })
    end

  private

    def empty_stats
      {
        :lines => 0, 
        :loc => 0, 
        :classes => 0, 
        :modules => 0, 
        :methods => 0, 
        :documentation => 0
        }
    end

    def stats_for_dir(directory, stats)
      pattern ||= @default_pattern
      Dir.foreach(directory) do |file_name|
        file = directory + "/" + file_name
        if File.stat(file).directory? and (/^\./ !~ file_name)
          stats = stats_for_dir(file, stats)
        end
        if file_name =~ @test_pattern
          stats_for_file(file, stats[:test])
        elsif file_name =~ @pattern
          stats_for_file(file, stats[:code])
        end
      end
      stats
    end


    def stats_for_file file, append_to_stats = nil
      stats = append_to_stats || empty_stats
      f = File.open(file)
      doc = 0
      while line = f.gets
        if line =~ /^\s*#/
          doc = 1
          next
        end
        if line =~ /class [A-Z]/
          stats[:classes]       += 1 
          stats[:documentation] += doc
        end
        if line =~ /def [a-z]/
          stats[:methods]       += 1 
          stats[:documentation] += doc
        end
        if line =~ /module [A-Z]/
          stats[:modules]       += 1
          stats[:documentation] += doc
        end
        doc = 0
        unless line =~ /^\s*$/
          stats[:loc] += 1 
        end        
        stats[:lines]         += 1
      end
      stats
    end
    
    def percent test, code
      if test > 0 && code > 0
        ((test*1.0/code*100).floor.to_s+'%')
      else
        '0%'
      end
    end
    
    def dots methods, classes
      if classes > 0
        s = ""
        m = ((methods*1.0/classes*10).floor/10.0)
        while m >= 2
          s << ':'
          m-=2
        end
        m.to_i.times { s << '.' }
        s
      else
        ''
      end
    end
    
    def ratio loc, methods
      return 0 if methods == 0
      loc -= methods*2
      ((loc*1.0/methods*10).floor/10.0)
    end
    
    def sum key1, key2
      sum = 0
      @stats.each { |stat| 
        sum += stat[key1][key2]
      }
      sum
    end

  public

    def print_report
      t = AsciiTable.new("Code statistics")
      t.columns = ['Group', 'LOC', 'CLS', 'M/C', 'MLOC', 'Test', 'Doc']
      @stats.each { |s|
        t.add_row [
          s[:label],
          s[:code][:loc],
          s[:code][:classes],
          dots(s[:code][:methods], s[:code][:classes]),
          dots(s[:code][:loc] - s[:code][:classes]* 2 - s[:code][:modules]*2, s[:code][:methods]),
          percent(s[:test][:loc], s[:code][:loc]),
          percent(s[:code][:documentation], s[:code][:methods] + s[:code][:classes])
        ]
      }
      sum = ['total',0,0,0,0,0.0,0.0]
      sum[1] =  sum(:code, :loc)
      sum[2] =  sum(:code, :classes)
      sum[3] =  ratio(sum(:code, :methods),sum(:code, :classes))
      #sum[4] =  methods_per_class(sum(:code, :loc), sum(:code, :methods))
      sum[4] = ratio(sum(:code, :loc) - sum(:code, :classes)* 2 - sum(:code, :modules)*2, sum(:code, :methods))
      sum[5] = percent(sum(:test, :loc), sum(:code, :loc))
      sum[6] = percent(sum(:code, :documentation), sum(:code, :methods) + sum(:code, :classes))
      t.add_divider
      t.add_row sum
      
      sum = ['tests' ,0,0,0,0,'', '']
      @stats.each { |stats|
        sum[1] += stats[:test][:loc]
        sum[2] += stats[:test][:classes]
        sum[3] += stats[:test][:methods]
        sum[4] += ratio(stats[:test][:loc], stats[:test][:methods])
      }
      sum[3] = (sum[3]*1.0/sum[2]*10.0).floor/10.0
      #sum[3] =  loc_per_method(sum(:test, :methods),sum(:test, :classes))
      sum[4] = (sum[4]/@stats.length*10).floor/10.0

      t.add_row sum
      puts t.render
    end
    
  end
end